/**
 * File MessageFactory.java created by Pablo Díaz Fernández
 * on 27/10/2011 for the project supply-chain-simulator
 */
package org.lsk.pfc.scsim.model.messages;

import java.util.StringTokenizer;

import jade.core.AID;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;

/**
 * @author Pablo Díaz Fernández
 * 
 */
public final class MessageFactory {

	/**
	 * TICK token.
	 */
	private static final String TICK_TOKEN = "**TICK**";
	/**
	 * Separator for tick information in a message.
	 */
	private static final String TICK_PREFIX = ";" + TICK_TOKEN + "=";

	/**
	 * Name of the ontology used in the syncronization of the agents.
	 */
	public static final String SYNC_ONTOLOGY = "__SYNC";
	/**
	 * Name of the ontology used in a MAS to give commands to its agents.
	 */
	public static final String COMMAND_ONTOLOGY = "__CMD";
	/**
	 * Name of the ontology used in the negociation among MAS.
	 */
	public static final String NEG_ONTOLOGY = "__NEG";

	/**
	 * Content of an End Of Tick message.
	 */
	public static final String EOT_MESSAGE = "__eot";

	/**
	 * Content of an End Of Execution message.
	 */
	public static final String EOE_MESSAGE = "__eoe";

	/**
	 * Content of a New Of Tick message.
	 */
	public static final String NT_MESSAGE = "__nt";

	/**
	 * Handles InformationAgent related messages.
	 */
	private static InformationAgentMessageFactory informationAgentMF =
			new InformationAgentMessageFactory();
	
	/**
	 * Handles negotiation related messages.
	 */
	private static NegotiationMessageFactory negotiationMF =
			new NegotiationMessageFactory();

	/**
	 * Private constructor to avoid instantiation.
	 */
	private MessageFactory() {
	}

	// TODO Check this construction in order to find timer in other containers
	/**
	 * Timer agent AID used to send messages to this agent.
	 */
	private static final AID TIMER_AGENT_AID = new AID(
	        MessageConstants.TIMER_AGENT_NAME, AID.ISLOCALNAME);

	/**
	 * Message template to match syndication request messages from other agents.
	 */
	public static final MessageTemplate SYND_REQ_MSG_TEMPLATE = MessageTemplate
	        .and(MessageTemplate.MatchOntology(SYNC_ONTOLOGY),
	                MessageTemplate.MatchPerformative(ACLMessage.SUBSCRIBE));

	/**
	 * Message template to match the confirmation of syndication sent by the
	 * timer.
	 */
	public static final MessageTemplate SYND_ACK_MSG_TEMPLATE = MessageTemplate
	        .and(MessageTemplate.MatchOntology(SYNC_ONTOLOGY),
	                MessageTemplate.MatchPerformative(ACLMessage.CONFIRM));

	/**
	 * Extracts the tick info from the message.
	 * 
	 * @param message
	 *            Message to parse.
	 * @return tick extracted or -1 if not found.
	 */
	public static long extractTick(final ACLMessage message) {
		StringTokenizer st = new StringTokenizer(message.getContent(), ";");
		while (st.hasMoreTokens()) {
			String aux = st.nextToken();
			if (aux.startsWith(TICK_TOKEN)) {
				return Long.parseLong(aux.substring(TICK_TOKEN.length(),
				        aux.length()));
			}
		}
		return -1;
	}

	/**
	 * Extracts the MAS name from an agent AID.
	 * 
	 * @param aid
	 *            {@link AID} to parse.
	 * @return MAS name extracted.
	 */
	public static String extractMASName(final AID aid) {
		return extractMASName(aid.getName());
	}

	/**
	 * Extracts the MAS name from an agent name.
	 * 
	 * @param aidName
	 *            Name to parse.
	 * @return MAS name extracted.
	 */
	public static String extractMASName(final String aidName) {
		return aidName.substring(0, aidName.lastIndexOf("_"));
	}

	/**
	 * Creates a syndication message to the timer agent.
	 * 
	 * @param agentName
	 *            Name of the agent that asks for syndication.
	 * @return {@link ACLMessage} Built.
	 */
	public static ACLMessage getSyndMsg(final String agentName) {
		ACLMessage msg = new ACLMessage(ACLMessage.SUBSCRIBE);
		msg.addReceiver(TIMER_AGENT_AID);
		msg.setOntology(SYNC_ONTOLOGY);
		msg.setContent(agentName);
		return msg;
	}

	/**
	 * Creates a confirmation for the syndication message.
	 * 
	 * @param agentName
	 *            Name of the agent that asks for syndication.
	 * @return {@link ACLMessage} Built.
	 */
	public static ACLMessage getConfirmSynMsg(final String agentName) {
		ACLMessage msg = new ACLMessage(ACLMessage.CONFIRM);
		msg.addReceiver(new AID(agentName, AID.ISGUID));
		msg.setOntology(SYNC_ONTOLOGY);
		msg.setContent(agentName);
		return msg;
	}

	/**
	 * Creates a message to the timer agent informing of the end of process in
	 * the current time tick.
	 * 
	 * @param agentName
	 *            Name of the agent that informs the timer of the end of tick.
	 * @return {@link ACLMessage} Built.
	 */
	public static ACLMessage getEOTMsg(final String agentName) {
		ACLMessage msg = new ACLMessage(ACLMessage.INFORM);
		msg.addReceiver(TIMER_AGENT_AID);
		msg.setOntology(SYNC_ONTOLOGY);
		msg.setContent(agentName);
		return msg;
	}

	/**
	 * Creates a forward message to another agent with the same data provided in
	 * the original message.
	 * 
	 * @param message
	 *            Original message to forward.
	 * @param from
	 *            {@link AID} of the agent that forwards the message.
	 * @param to
	 *            {@link AID} of the agent that has to receive the message
	 * @return message to forward.
	 */
	public static ACLMessage getForwardMessage(final ACLMessage message,
	        final AID from, final AID to) {
		ACLMessage forward = new ACLMessage(message.getPerformative());
		forward.setSender(from);
		forward.addReceiver(to);
		forward.setContent(message.getContent());
		forward.setOntology(message.getOntology());
		return forward;
	}

	/**
	 * Method to create tick based messages.
	 * 
	 * @param agentTarget
	 *            AID of the agent that should receive the message.
	 * @param agentSource
	 *            AID of the agent that sends the message.
	 * @param ontology
	 *            Ontology of the message.
	 * @param performative
	 *            Performative of the message.
	 * @param tickNo
	 *            number of the current time tick.
	 * @param content
	 *            content og the message.
	 * @return {@link ACLMessage} Built.
	 */
	protected static ACLMessage getTickBasedMessage(final AID agentTarget,
	        final AID agentSource, final String ontology,
	        final int performative, final Long tickNo, final String content) {
		ACLMessage msg = new ACLMessage(performative);
		msg.addReceiver(agentTarget);
		msg.addReplyTo(agentSource);
		msg.setOntology(ontology);
		msg.setContent(content + ';' + TICK_TOKEN + tickNo.toString());
		return msg;
	}

	/**
	 * Method to create basic messages.
	 * 
	 * @param agentNameTarget
	 *            Name of the agent that should receive the message.
	 * @param ontology
	 *            Ontology of the message.
	 * @param performative
	 *            Performative of the message.
	 * @param content
	 *            content og the message.
	 * @return {@link ACLMessage} Built.
	 */
	protected static ACLMessage getBasicMessage(final String agentNameTarget,
	        final String ontology, final int performative,
	        final String content) {
		ACLMessage msg = new ACLMessage(performative);
		msg.addReceiver(new AID(agentNameTarget, AID.ISGUID));
		msg.setOntology(ontology);
		msg.setContent(content);
		return msg;
	}

	/**
	 * Creates a new time tick message to the specified agent.
	 * 
	 * @param agentTarget
	 *            Name of the agent that should receive the message.
	 * @param agentSource
	 *            AID of the agent that sends the message.
	 * @param tickNo
	 *            Number of the current time tick.
	 * @return {@link ACLMessage} Built.
	 */
	public static ACLMessage getNewTickMsg(final AID agentTarget,
	        final AID agentSource, final Long tickNo) {
		return getTickBasedMessage(agentTarget, agentSource, SYNC_ONTOLOGY,
		        ACLMessage.INFORM, tickNo, NT_MESSAGE);
	}

	/**
	 * Creates a message to ask for price.
	 * 
	 * @param agentNameTarget
	 *            Name of the agent asked for price.
	 * @param agentNameSource
	 *            Name of the agent that asks for price.
	 * @param amount
	 *            Amount of items to buy.
	 * @return {@link ACLMessage} Built.
	 */
	public static ACLMessage getQueryPriceMSG(final String agentNameTarget,
	        final String agentNameSource, final float amount) {
		ACLMessage msg = new ACLMessage(ACLMessage.QUERY_IF);
		msg.addReceiver(new AID(agentNameTarget, AID.ISGUID));
		msg.setOntology(NEG_ONTOLOGY);
		msg.setContent(agentNameSource + "=" + amount);
		return msg;
	}

	/**
	 * Creates a message to ask for price.
	 * 
	 * @param agentNameTarget
	 *            Name of the agent asked for price.
	 * @param agentNameSource
	 *            Name of the agent that asks for price.
	 * @param amount
	 *            Amount of items to buy.
	 * @return {@link ACLMessage} Built.
	 */
	public static ACLMessage getConfirmPriceMSG(final String agentNameTarget,
	        final String agentNameSource, final float amount) {
		ACLMessage msg = new ACLMessage(ACLMessage.INFORM_IF);
		msg.addReceiver(new AID(agentNameTarget, AID.ISGUID));
		msg.setOntology(NEG_ONTOLOGY);
		msg.setContent(agentNameSource + "=" + amount);
		return msg;
	}



	/**
	 * Tests if the message matches the ontology and performative given as
	 * parameter.
	 * 
	 * @param msg
	 *            {@link ACLMessage} Message to test.
	 * @param ontology
	 *            {@link String} with the ontology to test. Null if no ontology
	 *            is needed.
	 * @param performative
	 *            Performative to test.
	 * @param content
	 *            Content of the message to test, if is <code>null</code> it
	 *            will not be tested.
	 * @return <code>true</code> if the message matches the parameters.
	 */
	public static boolean matches(final ACLMessage msg, final String ontology,
	        final int performative, final String content) {
		boolean m = msg.getPerformative() == performative;
		if (ontology != null) {
			m = m && msg.getOntology().equals(ontology);
		}
		if (content != null) {
			m = m && msg.getContent().startsWith(content);
		}
		return m;
	}

	/**
	 * Tests if the message matches a syndication request.
	 * 
	 * @param msg
	 *            {@link ACLMessage} Message to test.
	 * @return <code>true</code> if the message is a syndication request.
	 */
	public static boolean isSyndicationReq(final ACLMessage msg) {
		return MessageFactory.matches(msg, SYNC_ONTOLOGY, ACLMessage.SUBSCRIBE,
		        null);
	}

	/**
	 * Tests if the message matches an End-Of-Tick information.
	 * 
	 * @param msg
	 *            {@link ACLMessage} Message to test.
	 * @return <code>true</code> if the message is an End-Of-Tick information.
	 */
	public static boolean isEOT(final ACLMessage msg) {
		return MessageFactory.matches(msg, SYNC_ONTOLOGY, ACLMessage.INFORM,
		        EOT_MESSAGE);
	}

	/**
	 * Tests if the message matches an End-Of-Execution information.
	 * 
	 * @param msg
	 *            {@link ACLMessage} Message to test.
	 * @return <code>true</code> if the message is an End-Of-Execution
	 *         information.
	 */
	public static boolean isEOE(final ACLMessage msg) {
		return MessageFactory.matches(msg, SYNC_ONTOLOGY, ACLMessage.INFORM,
		        EOE_MESSAGE);
	}

	/**
	 * Tests if the message matches a new tick message.
	 * 
	 * @param msg
	 *            {@link ACLMessage} Message to test.
	 * @return <code>true</code> if the message is a new tick notification.
	 */
	public static boolean isNewTick(final ACLMessage msg) {
		return MessageFactory.matches(msg, SYNC_ONTOLOGY, ACLMessage.INFORM,
		        NT_MESSAGE);
	}

	/**
	 * Gets the informationAgentMF.
	 * 
	 * @return the informationAgentMF
	 */
	public static InformationAgentMessageFactory getInformationAgentMF() {
		return informationAgentMF;
	}

	/**
     * Gets the negotiationMF.
     *
     * @return the negotiationMF
     */
    public static NegotiationMessageFactory getNegotiationMF() {
    	return negotiationMF;
    }

}
